package com.lin.generator;

import com.lin.generator.config.GeneratorConfig;
import com.lin.generator.model.Field;
import com.lin.generator.model.Out;
import com.lin.generator.model.Table;
import com.lin.generator.model.Template;
import com.lin.generator.service.AbstractService;
import com.lin.generator.service.MysqlService;
import com.lin.generator.service.OracleService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ResourceUtils;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * @author 猴哥
 */
public class GeneratorUtil {
	/**
	 * 生成器配置
	 */
	private static GeneratorConfig config;

	/**
	 * 数据层服务
	 */
	private static AbstractService service;

	/**
	 * 类型映射
	 */
	private static Map<String, List<String>> typeMap;

	/**
	 * mysql数据库名称
	 */
	private static String dbName;

	/**
	 * 配置文件
	 */
	@Resource
	private GeneratorConfig generatorConfig;

	@Value("${spring.datasource.url}")
	private String url;

	/**
	 * 数据层服务
	 */
	@Resource
	private MysqlService mysqlService;
	@Resource
	private OracleService oracleService;

	/**
	 * 正则
	 */
	private static final Pattern FIELDS_PATTERN = Pattern.compile("(?<=(<fields>)).*?(?=(</fields>))", Pattern.DOTALL);
	private static final Pattern PRIMARY_PATTERN = Pattern.compile("<primary-key>.*?</primary-key>", Pattern.DOTALL);
	private static final Pattern NOT_PRIMARY_PATTERN = Pattern.compile("<not-primary-key>.*?</not-primary-key>", Pattern.DOTALL);

	/**
	 * 初始化
	 */
	@PostConstruct
	public void init() {
		String mysqlStr = "jdbc:mysql:";
		String oracleStr = "jdbc:oracle:";

		if (url.startsWith(mysqlStr)) {
			service = mysqlService;
			typeMap = generatorConfig.getMysqlTypeMap();

			// 获取数据库名称
			dbName = getDbName(url);
		} else if (url.startsWith(oracleStr)) {
			service = oracleService;
			typeMap = generatorConfig.getOracleTypeMap();
		} else {
			throw new RuntimeException("不支持的数据库类型");
		}

		// 进行生成
		generator();
	}

	/**
	 * 初始化生成器
	 *
	 * @param generatorConfig 生成器配置
	 */
	public GeneratorUtil(GeneratorConfig generatorConfig) {
		config = generatorConfig;
	}

	/**
	 * 生成代码
	 */
	public static void generator() {
		// 获取表数据和字段数据
		List<Table> tableList = service.getTables(dbName, config.getRemovePrefixes(), config.getGeneratorTables(), typeMap);
		// 读取模板
		List<Template> templateList = config.getTemplateList();
		if (CollectionUtils.isEmpty(templateList)) {
			return;
		}

		// 进行生成
		templateList.forEach(template -> generator(template.getTemplatePath(), template.getTargetPath(), tableList));
	}

	/**
	 * 生成代码
	 *
	 * @param templatePath 模板路径
	 * @param targetPath 目标路径
	 * @param tableList 所有表和字段数据
	 */
	private static void generator(String templatePath, String targetPath, List<Table> tableList) {
		// 读取java下的所有文件
		File templateDirectory;
		try {
			// 模板文件目录
			templateDirectory = templatePath.startsWith("classpath") ? ResourceUtils.getFile(templatePath) : new File(templatePath);
		} catch (FileNotFoundException e) {
			System.out.println("代码生成失败，找不到模板路径：" + templatePath);
			return;
		}
		// 获取要解析的文件列表
		List<File> fileList = readFiles(templateDirectory, new LinkedList<>());
		// 获取输出的文件内容
		List<List<Out>> outList = getOutputFile(templateDirectory.getAbsolutePath(), targetPath, fileList, tableList);
		// 解析模板
		parse(outList);
		// 进行输出
		output(outList);
		System.out.println("代码生成成功：" + targetPath);
	}

	/**
	 * 读取某个目录下的所有文件
	 *
	 * @param directory 目录
	 * @param fileList 文件列表
	 * @return 目录下的所有文件
	 */
	private static List<File> readFiles(File directory, List<File> fileList) {
		if (directory.isDirectory()) {
			for (File file : Objects.requireNonNull(directory.listFiles())) {
				readFiles(file, fileList);
			}
		} else {
			fileList.add(directory);
		}
		return fileList;
	}

	/**
	 * 获取输出的文件内容
	 *
	 * @param templatePath 模板路径
	 * @param targetPath 目标路径
	 * @param fileList 模板文件列表
	 * @param tableList 所有数据表
	 * @return 输出的文件内容
	 */
	private static List<List<Out>> getOutputFile(String templatePath, String targetPath, List<File> fileList, List<Table> tableList) {
		// 遍历模板文件
		return fileList
				.stream()
				.map(file -> {
					// 模板内容
					String templateContent= readFile(file);

					// 文件名
					String fileName = "Model.java".equals(file.getName()) ? ".java" : file.getName();

					// 每个模板生成的文件（有多少张表就有多少个文件）
					List<Out> outList = new LinkedList<>();

					// 遍历每张表
					tableList.forEach(table -> {
						Out out = new Out();
						out.setDirectory(getOutDirectory(templatePath, file.getParent(), targetPath));
						out.setFileName(table.getUpperCamel() + fileName);
						out.setFileContent(templateContent);
						out.setTable(table);
						outList.add(out);
					});

					return outList;
				})
				.collect(Collectors.toList());
	}

	/**
	 * 读取文件内容
	 *
	 * @param file 文件
	 * @return 文件内容
	 */
	private static String readFile(File file) {
		try {
			// 读取文件内容
			FileInputStream in = new FileInputStream(file);
			ByteBuffer byteBuffer = ByteBuffer.allocate((int) file.length());
			FileChannel channel = in.getChannel();
			channel.read(byteBuffer);
			String content = new String(byteBuffer.array());
			in.close();
			return content;
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}

	/**
	 * 获取输出目录
	 *
	 * @param templateDirectory 模板根目录
	 * @param templateFilePath 模板文件路径
	 * @param targetPath 目标路径
	 * @return 输出目录
	 */
	private static File getOutDirectory(String templateDirectory, String templateFilePath, String targetPath) {
		templateDirectory = templateDirectory.replace("\\", "/");
		templateFilePath = templateFilePath.replace("\\", "/");
		targetPath = targetPath.replace("\\", "/");

		String relative = templateFilePath.replace(templateDirectory, targetPath);
		return new File(relative);
	}

	/**
	 * 进行输出
	 *
	 * @param outList 输出列表
	 */
	private static void output(List<List<Out>> outList) {
		outList.forEach(out1 -> {
			out1.forEach(out2 -> {
				try {
					// 创建目录
					if (!out2.getDirectory().exists()) {
						boolean mkdirs = out2.getDirectory().mkdirs();
					}

					// 输出到文件
					FileOutputStream out = new FileOutputStream(new File(out2.getDirectory(), out2.getFileName()));
					FileChannel channel = out.getChannel();
					ByteBuffer byteBuffer = ByteBuffer.allocate(out2.getFileContent().getBytes().length);
					byteBuffer.put(out2.getFileContent().getBytes());
					byteBuffer.flip();
					channel.write(byteBuffer, byteBuffer.position());
					out.close();
				} catch (IOException e) {
					throw new RuntimeException(e);
				}
			});
		});
	}

	/**
	 * 截取数据库名称
	 *
	 * @param url 数据库连接信息
	 * @return 数据库名称
	 */
	private String getDbName(String url) {
		String w = "?";
		if (url.contains(w)) {
			url = url.substring(0, url.indexOf(w));
		}
		return url.substring(url.lastIndexOf("/") + 1);
	}

	/**
	 * 解析模板
	 *
	 * @param outList 输出文件列表
	 */
	private static void parse(List<List<Out>> outList) {
		outList.forEach(out1 -> {
			out1.forEach(out2 -> {
				// 获取整个模板内容
				String fileContent = out2.getFileContent();
				Table table = out2.getTable();
				fileContent = fileContent
						.replace("<table-name/>", table.getTableName())
						.replace("<TableName/>", table.getUpperCamel())
						.replace("<tableName/>", table.getLowerCamel())
						.replace("<table-comment/>", table.getTableComment() == null ? table.getTableName() : table.getTableComment())
						.replace("<author/>", config.getAuthor())
						.replace("<package-name/>", config.getPackageName())
						.replace("<to-string/>", getToString(table));

				// 取出所有字段的模板
				Matcher matcher = FIELDS_PATTERN.matcher(fileContent);

				// 遍历匹配到的fields标签
				while (matcher.find()) {
					// 字段模板
					String fieldTemplate = matcher.group();
					// 每个字段的文字
					StringBuilder fieldStr = new StringBuilder();

					// 遍历所有字段
					for (Field field : table.getFieldList()) {
						// 根据模板生成一份字段的内容字符串
						String fieldContent = fieldTemplate;

						fieldContent = primaryKeyHandler(field, fieldContent);

						fieldStr.append(fieldContent
								.replace("<field-name/>", field.getFieldName())
								.replace("<fieldName/>", field.getLowerCamel())
								.replace("<FieldName/>", field.getUpperCamel())
								.replace("<field-comment/>", field.getFieldComment() == null ? field.getFieldName() : field.getFieldComment())
								// TODO 写好映射关系之后，Object要改成String
								.replace("<javaType/>", field.getJavaType() == null ? "String" : field.getJavaType())
								.replace("<primary-key>", "")
								.replace("</primary-key>", "")
								.replace("<not-primary-key>", "")
								.replace("</not-primary-key>", "")
						);
					}
					fileContent = fileContent.replace(fieldTemplate, removeCommas(fieldStr.toString()));
					// 处理导入的包
					fileContent = importFieldTypeHandler(table, fileContent);
				}
				fileContent = fileContent.replace("<fields>", "");
				fileContent = fileContent.replace("</fields>", "");
				out2.setFileContent(fileContent);
			});
		});
	}

	/**
	 * 生成toString
	 *
	 * @param table 表信息
	 * @return 生成的toString
	 */
	private static String getToString(Table table) {
		StringBuilder sb = new StringBuilder()
				.append("@Override\n")
				.append("\tpublic String toString() {\n")
				.append("\t\treturn \"").append(table.getUpperCamel()).append("{\" +\n");

		for (int i = 0; i< table.getFieldList().size(); i++) {
			Field field = table.getFieldList().get(i);
			sb.append("\t\t\t\"")
					.append(field.getLowerCamel())
					.append("='\" + ")
					.append(field.getLowerCamel())
					.append(" + \"'");
			// 如果是最后一条
			if (i != table.getFieldList().size() - 1) {
				sb.append(", ");
			}
			sb.append("\" +\n");
		}

		sb.append("\t\t\t'}';\n")
				.append("\t}");
		return sb.toString();
	}

	/**
	 * 处理主键
	 *
	 * @param field 字段
	 * @param fieldContent 内容字符串
	 * @return 处理后的数据
	 */
	private static String primaryKeyHandler(Field field, String fieldContent) {
		// 取出<primary-key>标签
		Matcher primaryMatcher = PRIMARY_PATTERN.matcher(fieldContent);
		// 如果不是主键就把primary-key里面的内容去掉
		while (primaryMatcher.find()) {
			if (!field.getPrimaryKey()) {
				fieldContent = fieldContent.replace(primaryMatcher.group(), "");
			}
		}

		// 取出<primary-key>标签
		Matcher notPrimaryMatcher = NOT_PRIMARY_PATTERN.matcher(fieldContent);
		// 如果是主键就把primary-key里面的内容去掉
		while (notPrimaryMatcher.find()) {
			if (field.getPrimaryKey()) {
				fieldContent = fieldContent.replace(notPrimaryMatcher.group(), "");
			}
		}
		return fieldContent;
	}

	/**
	 * 导入字段类型
	 *
	 * @param table 表数据
	 * @param fileContent 文件内容
	 * @return 处理后的数据
	 */
	private static String importFieldTypeHandler(Table table, String fileContent) {
		for (String javaType : config.getImportMap().keySet()) {
			String importValue = config.getImportMap().get(javaType);
			boolean hasJavaType = table
					.getFieldList()
					.stream()
					.anyMatch(field -> javaType.equals(field.getJavaType()));
			if (hasJavaType) {
				fileContent = fileContent.replace("<import-field-type/>", "import " + importValue + ";\n");
			}
		}
		return fileContent;
	}

	/**
	 * 去除结尾的逗号
	 *
	 * @param str 字符串
	 * @return 去除后的结果
	 */
	private static String removeCommas(String str) {
		// 如果结尾没逗号
		String d = ",";
		if (!str.trim().endsWith(d)) {
			return str;
		}
		// 获取逗号的索引
		int index = str.lastIndexOf(d);
		return str.substring(0, index);
	}
}
